/*
 * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * The Universal Permissive License (UPL), Version 1.0
 *
 * Subject to the condition set forth below, permission is hereby granted to any
 * person obtaining a copy of this software, associated documentation and/or
 * data (collectively the "Software"), free of charge and under any and all
 * copyright rights in the Software, and any and all patent rights owned or
 * freely licensable by each licensor hereunder covering either (i) the
 * unmodified Software as contributed to or provided by such licensor, or (ii)
 * the Larger Works (as defined below), to deal in both
 *
 * (a) the Software, and
 *
 * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
 * one is included with the Software each a "Larger Work" to which the Software
 * is contributed by such licensors),
 *
 * without restriction, including without limitation the rights to copy, create
 * derivative works of, display, perform, and distribute the Software and make,
 * use, sell, offer for sale, import, export, have made, and have sold the
 * Software and the Larger Work(s), and to sublicense the foregoing rights on
 * either these or other terms.
 *
 * This license is subject to the following condition:
 *
 * The above copyright notice and either this complete permission notice or at a
 * minimum a reference to the UPL must be included in all copies or substantial
 * portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package org.graalvm.nativebridge;

import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Supplier;

/**
 * A configuration used by the {@link NativeIsolate} and classes generated by the native bridge
 * processor.
 *
 * @see GenerateHotSpotToNativeBridge
 * @see GenerateNativeToNativeBridge
 */
public final class NativeIsolateConfig {

    private final Path isolateLibrary;
    private final Map<String, Object> nativeIsolateHandlerOptions;
    private final Supplier<ThreadLocal<NativeIsolateThread>> threadLocalFactory;
    private final Consumer<? super NativeIsolate> onIsolateTearDown;

    private NativeIsolateConfig(Path isolateLibrary,
                    Map<String, Object> nativeIsolateHandlerOptions,
                    Supplier<ThreadLocal<NativeIsolateThread>> threadLocalFactory,
                    Consumer<? super NativeIsolate> onIsolateTearDown) {
        this.isolateLibrary = isolateLibrary.toAbsolutePath();
        this.nativeIsolateHandlerOptions = nativeIsolateHandlerOptions;
        this.threadLocalFactory = threadLocalFactory;
        this.onIsolateTearDown = onIsolateTearDown;
    }

    /**
     * Returns a path to the isolate library used by the native isolate.
     */
    public Path getIsolateLibrary() {
        return isolateLibrary;
    }

    /**
     * Retrieves the value of the specified {@code key} option for
     * {@link NativeIsolateHandler#createIsolate(NativeIsolateConfig)}.
     *
     * <p>
     * The option values are configured using
     * {@link Builder#nativeIsolateHandlerOption(String, Object)}.
     * </p>
     *
     * @throws NullPointerException if {@code key} is {@code null}
     * @throws NoSuchElementException if the specified option was not set in the {@link Builder}
     * @see NativeIsolateHandler#createIsolate(NativeIsolateConfig)
     * @see Builder#nativeIsolateHandlerOption(String, Object)
     */
    public Object getNativeIsolateHandlerOption(String key) {
        Objects.requireNonNull(key, "Key must be non-null");
        if (nativeIsolateHandlerOptions.containsKey(key)) {
            return nativeIsolateHandlerOptions.get(key);
        } else {
            throw new NoSuchElementException("The option " + key + " was not set in the Builder.");
        }
    }

    Supplier<ThreadLocal<NativeIsolateThread>> getThreadLocalFactory() {
        return threadLocalFactory;
    }

    Consumer<? super NativeIsolate> getOnIsolateTearDownHook() {
        return onIsolateTearDown;
    }

    /**
     * Creates a new builder for constructing a {@link NativeIsolateConfig}.
     */
    public static Builder newBuilder(Path isolateLibrary) {
        return new Builder(isolateLibrary);
    }

    /**
     * A builder class to construct {@link NativeIsolateConfig} instances.
     */
    public static final class Builder {

        private final Path isolateLibrary;
        private final Map<String, Object> nativeIsolateHandlerOptions;
        private Supplier<ThreadLocal<NativeIsolateThread>> threadLocalFactory = ThreadLocal::new;
        private Consumer<? super NativeIsolate> isolateTearDownHandler;

        private Builder(Path isolateLibrary) {
            this.isolateLibrary = Objects.requireNonNull(isolateLibrary, "IsolateLibrary must be non-null.");
            this.nativeIsolateHandlerOptions = new HashMap<>();
        }

        /**
         * Registers a thread local factory whenever the default thread local handling should be
         * overridden. This can be useful to install a terminating thread local using JVMCI services
         * when needed.
         *
         * @see NativePeer
         */
        public Builder nativeThreadLocalFactory(Supplier<ThreadLocal<NativeIsolateThread>> factory) {
            Objects.requireNonNull(factory, "Action must be non null.");
            this.threadLocalFactory = factory;
            return this;
        }

        /**
         * Adds a key-value option for configuring the {@link NativeIsolateHandler}.
         *
         * <p>
         * The specified option can be retrieved later using
         * {@link NativeIsolateConfig#getNativeIsolateHandlerOption(String)} when creating an
         * isolate with {@link NativeIsolateHandler#createIsolate(NativeIsolateConfig)}.
         * </p>
         *
         * @param key the name of the option; must not be {@code null}
         * @param value the value associated with the option
         * @see NativeIsolateConfig#getNativeIsolateHandlerOption(String)
         * @see NativeIsolateHandler#createIsolate(NativeIsolateConfig)
         */
        public Builder nativeIsolateHandlerOption(String key, Object value) {
            Objects.requireNonNull(key, "Key must be non-null.");
            this.nativeIsolateHandlerOptions.put(key, value);
            return this;
        }

        /**
         * Registers a hook called before the native isolate is closed.
         */
        public Builder onIsolateTearDown(Consumer<? super NativeIsolate> handler) {
            Objects.requireNonNull(handler, "Handler must be non-null");
            this.isolateTearDownHandler = handler;
            return this;
        }

        /**
         * Builds the {@link NativeIsolateConfig}.
         */
        public NativeIsolateConfig build() {
            return new NativeIsolateConfig(isolateLibrary, nativeIsolateHandlerOptions,
                            threadLocalFactory, isolateTearDownHandler);
        }
    }
}
